#ifndef GAMEENGINE_GEOMETRY_LINE2D_HPP
#define GAMEENGINE_GEOMETRY_LINE2D_HPP

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================
// Implementation of the Line2d class

//! Default constructor
/*!
 @note Class T should have a default constructor
 */
template <class T>
Line2d<T>::Line2d() {}

//! Constructor from two points
/*!
 @param p1 The first point
 @param p2 The second point
 @note Class T should have a default constructor
 @note Class T should have an assignment operator
 */
template <class T>
Line2d<T>::Line2d(const Point2d<T>& p1, const Point2d<T>& p2) {
	points_[0] = p1;
	points_[1] = p2;
}

//! Copy constructor
/*!
 @param rhs The line to copy
 @note Class T should have a default constructor
 @note Class T should have an assignment operator
 */
template <class T>
Line2d<T>::Line2d(const Line2d& rhs) {
	for ( int k = 0; k < 2; ++k ) {
		points_[k] = rhs.points_[k];
	}
}

//! Destructor
/*!
 @note The destructor of class T should be public
 */
template <class T>
Line2d<T>::~Line2d() {}

//! Assignment operator
/*!
 @param rhs The line to copy
 @note Class T should have an assignment operator
 */
template <class T>
Line2d<T>& Line2d<T>::operator=(const Line2d& rhs) {
	if ( &rhs != this ) {
		for ( int k = 0; k < 2; ++k ) {
			points_[k] = rhs.points_[k];
		}
	}
	return *this;
}

//! Accessor to the first point (read-write)
/*!
 @return A reference to the first point
 */
template <class T>
Point2d<T>& Line2d<T>::p1() {
	return points_[0];
}

//! Accessor to the first point (read only)
/*!
 @return A constant reference to the first point
 */
template <class T>
const Point2d<T>& Line2d<T>::p1() const {
	return points_[0];
}

//! Accessor to the second point (read-write)
/*!
 @return A reference to the second point
 */
template <class T>
Point2d<T>& Line2d<T>::p2() {
	return points_[1];
}

//! Accessor to the second point (read only)
/*!
 @return A constant reference to the second point
 */
template <class T>
const Point2d<T>& Line2d<T>::p2() const {
	return points_[1];
}

//! Accessor to the given point (read-write)
/*!
 @return A reference to the given point
 */
template <class T>
Point2d<T>& Line2d<T>::operator[](Line2D::Index index) {
	return points_[(int)index];
}

//! Accessor to the given point (read only)
/*!
 @return A constant reference to the given point
 */
template <class T>
const Point2d<T>& Line2d<T>::operator[](Line2D::Index index) const {
	return points_[(int)index];
}

//! Compute the point location along the line given a parametric coordinate
/*!
 @param coordinate The target parametric coordinate
 @return The point location along the line given a parametric coordinate
 */
template <class T>
Point2d<T> Line2d<T>::point(const T& coordinate) const {
	return Vector2d<T>( points_[0], points_[1] ) * coordinate + points_[0];
}

//! Compute the intersection between the two lines
/*!
 @param rhs The line to intersect with
 @param coordinate1 This reference receives the parametric coordinate of the intersection, if
					any. If you want to get the intersection point rather than its parametric
					coordinate, call the point() function on this line
 @param coordinate2 If not nil, the content of this pointer receives the parametric coordinate
					of the intersection along the rhs line, if any intersection. If you want to
					get the intersection point rather than its parametric coordinate, call the
					point() function on the rhs line
 @return The intersection code of the two lines. The returned value can either be Parallel if the two
         lines do not intersect, Intersection if the two lines do intersect, or DegenerateIntersection
		 if any of the lines is degenerate (hence, that intersection does not make sense)
 @note Since the two lines are supposed to have an infinite extension, the ExternIntersection code
       cannot be returned with this type of element
 */
template <class T>
IntersectionType::Type Line2d<T>::intersection(const Line2d& rhs, T& coordinate1, T* coordinate2) const {
	// Check that lines are not degenerate
	if ( degenerate() ) return IntersectionType::DegenerateIntersection;
	if ( rhs.degenerate() ) return IntersectionType::DegenerateIntersection;

	// Get denominator
	T denom = -p1().y()*rhs.p1().x() + p2().y()*rhs.p1().x() + p1().y()*rhs.p2().x() - p2().y()*rhs.p2().x()
			 + p1().x()*rhs.p1().y() - p2().x()*rhs.p1().y() - p1().x()*rhs.p2().y() + p2().x()*rhs.p2().y();

	// Check that the denominator is not null (i.e. that the lines are not parallel
	if ( denom == 0.0f ) {
		return IntersectionType::Parallel;
	}
	
	// Compute parametric coordinates
	coordinate1 = -( p1().y()*rhs.p1().x() - p1().y()*rhs.p2().x() - p1().x()*rhs.p1().y()
		+ rhs.p2().x()*rhs.p1().y() + p1().x()*rhs.p2().y() - rhs.p1().x()*rhs.p2().y()) / denom;
	if ( coordinate2 )
		*coordinate2 = ( p2().x()*p1().y() - p1().x()*p2().y() - p1().y()*rhs.p1().x()
					   + p2().y()*rhs.p1().x() + p1().x()*rhs.p1().y() - p2().x()*rhs.p1().y()) / denom;
	return IntersectionType::Intersection;
}

//! Compute the point location along the line that is the closest to given point
/*!
 @param rhs The point to project on the line
 @return The parametric coordinate of the closest point location along the line
 */
template <class T>
T Line2d<T>::closest_point(const Point2d<T>& rhs) const {
	T denom = p1().get_square_distance(p2());
	T num = (rhs.x() - p1().x())*(p1().x() - p2().x()) + (rhs.y() - p1().y())*(p1().y() - p2().y());
	return -num / denom;
}

//! Check whether the line is degenerate
/*!
 @return True if the line is degenerate, false otherwise
 @note The line is degenerate if its first and second points
       have exactly the same location
 */
template <class T>
bool Line2d<T>::degenerate() const {
	return ( p1().get_square_distance(p2()) <= 0 );
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
